import math
import numpy as np
import pandas as pd

# 读取数据集和标签
df = pd.read_csv('watermelon_5_3.csv')
data = df.values[:, 1:-1].tolist()
labels = df.values[:, -1].tolist()

# 每层神经元的输入
input_layer = np.array([[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],[0.0]]) # 8个输入神经元
hidden_layer = np.array([[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],[0.0]]) # 9个隐层神经元
output_layer = np.array([[0.0]]) # 1个输出神经元

# 初始化输入到隐层、隐层到输出的权重和阈值
hidden_weight = np.random.rand(len(input_layer), len(hidden_layer))
hidden_threshold = np.random.rand(len(hidden_layer), 1)
output_weight = np.random.rand(len(hidden_layer), len(output_layer))
output_threshold = np.random.rand(len(output_layer), 1)

# 标准BP算法
old_loss = 0.0 # 上次迭代的误差
step = 0
stable_time = 0 # 误差保持不变的迭代次数
learning_rate = 0.1
while 1:
    step += 1
    cur_loss = 0.0 # 当前样本的误差
    for sample_i in range(len(data)):
        # 计算输入层的值
        for i in range(len(input_layer)):
            input_layer[i][0] = data[sample_i][i]

        # 计算隐藏层的值
        alpha = np.dot(hidden_weight.T, input_layer) - hidden_threshold
        for i in range(len(hidden_layer)):
            hidden_layer[i] = 1 / (1 + math.exp(-alpha[i]))

        # 计算输出层的值
        beta = np.dot(output_weight.T, hidden_layer) - output_threshold
        output_layer = 1 / (1 + math.exp(-beta))

        # 计算误差
        cur_loss += (output_layer - labels[sample_i]) * (output_layer - labels[sample_i]) / 2.0 # 公式(5.4)

        # 更新权值
        g = output_layer * (1 - output_layer) * (labels[sample_i] - output_layer) # 公式(5.10)
        delta_output_weight = learning_rate * np.dot(g, hidden_layer) # 公式(5.11)
        delta_output_threshold = -learning_rate * g # 公式(5.12)

        e = hidden_layer * (1 - hidden_layer) * np.dot(g, output_weight) # 公式(5.15)
        delta_hidden_weight = learning_rate * np.dot(input_layer, e.T) # 公式(5.13)
        delta_hidden_threshold = -learning_rate * e # 公式(5.14)

        output_weight += delta_output_weight
        output_threshold += delta_output_threshold
        hidden_weight += delta_hidden_weight
        hidden_threshold += delta_hidden_threshold

    if abs(cur_loss - old_loss) < 0.0001:
        stable_time += 1
        if stable_time == 100:
            # 误差在100轮迭代中保持不变
            break
    else:
        old_loss = cur_loss
        stable_time = 0


# 对训练集进行测试
predict = []
for sample in range(len(data)):
    for i in range(len(input_layer)):
        input_layer[i][0] = data[sample][i]
    
    alpha = np.dot(hidden_weight.T, input_layer) - hidden_threshold
    for i in range(len(hidden_layer)):
        hidden_layer[i] = 1 / (1 + math.exp(-alpha[i]))

    beta = np.dot(output_weight.T, hidden_layer) - output_threshold
    output_layer = 1 / (1 + math.exp(-beta))

    predict.append(output_layer)

print('predict\t\t\tlabels')
for i in range(len(predict)):
    print(predict[i], '\t', labels[i])
